import 'dart:math';

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:json/json.dart';
import 'package:json_generator/extension/extension.dart';
import 'package:source_gen/source_gen.dart';



class JsonGenerator extends GeneratorForAnnotation<Json>{

  final bool useStatic;
  final bool useSnakeCase;
  const JsonGenerator({required this.useStatic, required this.useSnakeCase});

  static const String comma = ",";
  static const String lineDelimiter = "\n";
  @override
  String generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) {
    ClassElement? classElement;
    if(element is ClassElement){
      classElement = element;
    }
    StringBuffer all = StringBuffer("//${element.runtimeType}$lineDelimiter");
    if(classElement != null){
      // all.write('import ${classElement.displayName}.dart;$lineDelimiter');
      // all.write('mixin ${classElement.displayName}Mixin on ${classElement.displayName}{$lineDelimiter');
      int fieldIndex = 0;
      List<FieldElement> fields = classElement.fields;
      int fieldsLen = fields.length;
      String beanName = lowerFirstCharacter(classElement.displayName);
      String temp = '${classElement.displayName}${classElement.typeParameters.isNotEmpty ? '<${classElement.typeParameters.first}>' : ''}';
      StringBuffer fromMap = StringBuffer(
          '${useStatic ? '$temp?' : 'void'} _fromMap${classElement.typeParameters.isNotEmpty ? '<${classElement.typeParameters.first}>' : ''}(Map<String, dynamic>? map${useStatic ? '' : ', $temp $beanName'}){$lineDelimiter'
              + (useStatic ? 'if(map == null) return null;$lineDelimiter' : '')
              + (useStatic ? '${classElement.displayName}${classElement.typeParameters.isNotEmpty ? '<${classElement.typeParameters.first}>' : ''} $beanName = ${classElement.displayName}();$lineDelimiter' : '')
              + '${classElement.typeParameters.isNotEmpty ? 'bool isInt = T == int;${lineDelimiter}bool isDouble = T == double;$lineDelimiter' : ''}'
      );
      StringBuffer? fromDBMap;
      if(useSnakeCase) fromDBMap = StringBuffer(
          '${useStatic ? '$temp?' : 'void'} _fromDBMap${classElement.typeParameters.isNotEmpty ? '<${classElement.typeParameters.first}>' : ''}(Map<String, dynamic>? map${useStatic ? '' : ', $temp $beanName'}){$lineDelimiter'
              + (useStatic ? 'if(map == null) return null;$lineDelimiter' : '')
              + (useStatic ? '${classElement.displayName}${classElement.typeParameters.isNotEmpty ? '<${classElement.typeParameters.first}>' : ''} $beanName = ${classElement.displayName}();$lineDelimiter' : '')
              + '${classElement.typeParameters.isNotEmpty ? 'bool isInt = T == int;${lineDelimiter}bool isDouble = T == double;$lineDelimiter' : ''}'
      );
      StringBuffer toJson = StringBuffer();
      // StringBuffer toJsonParams = StringBuffer();
      for(FieldElement fieldElement in fields){
        if(fieldElement.isStatic || fieldElement.isConst || fieldElement.isFinal){
          fieldIndex++;
          continue;
        }
        all.write('// ${fieldElement.type} ${fieldElement.displayName} isSynthetic ${fieldElement.isSynthetic}$lineDelimiter');
        if(!fieldElement.isSynthetic){
          // if(useStatic) toJsonParams.write(handleToJsonParam(fieldElement));
          toJson.write(handleToJson(fieldElement, beanName));
          fromMap.write(handleFromMap(beanName, fieldElement, classElement));
          fromDBMap?.write(handleFromMap(beanName, fieldElement, classElement, useSnakeCase: true));
        }
        if(fieldIndex + 1 < fieldsLen){
          // if(useStatic) toJsonParams.write(comma);
          if(!fieldElement.isSynthetic){
            toJson.write(comma);
            fromMap.write(lineDelimiter);
            fromDBMap?.write(lineDelimiter);
          }
        }else{
          if(useStatic) {
            fromMap.write("${lineDelimiter}return $beanName;$lineDelimiter}");
            fromDBMap?.write("${lineDelimiter}return $beanName;$lineDelimiter}");
          }else{
            fromMap.write("$lineDelimiter}");
            fromDBMap?.write("$lineDelimiter}");
          }
        }
        fieldIndex ++;
      }
      all.write(fromMap.toString());
      all.write(lineDelimiter);
      if(fromDBMap != null){
        all.write(fromDBMap.toString());
        all.write(lineDelimiter);
      }
      all.write('Map<String, dynamic> _${useStatic ? 'toJson' : 'toMap'}${classElement.typeParameters.isNotEmpty ? '<${classElement.typeParameters.first}>' : ''}('
          // '${useStatic ? toJsonParams.toString() : '$temp $beanName'}'
          '$temp $beanName'
          ') =>'
          ' <String, dynamic>{${toJson.toString()}};$lineDelimiter');
      // all.write('}');

    }


    return all.toString();
  }

  String handleFromMap(String bean, FieldElement e, ClassElement c, {bool useSnakeCase = false}){
    String map = useStatic ? 'map' : 'map?';
    String displayName = useSnakeCase ? e.displayName.snakeCase : e.displayName;
    String? str;
    if(e.type.isDartCoreList){
      // try{
      //   InterfaceElement? interface;
      //   if(e.type is InterfaceElement){
      //     interface = e.type as InterfaceElement?;
      //   }
      //   str = '//${e.type} ${e} ${interface}$lineDelimiter';
      //   print(str);
      // }catch(e){
      //   print(e);
      // }
      InterfaceType? interfaceType;
      if(e.type is InterfaceType){
        interfaceType = e.type as InterfaceType?;
      }
      DartType? type = interfaceType?.typeArguments.firstOrNull;
      str = '// ${e} ${e.type.runtimeType} ${interfaceType?.typeArguments} ${canHaveGenerics(e.type)}$lineDelimiter';
      str += '// ${type} ${type?.isDartCoreEnum} ${(type?.isDartCoreEnum)??false} ${type?.isBottom} ${type?.isDartCoreObject} ${type?.isDartCoreType}$lineDelimiter';
      if(type?.isDartCoreString??false){
        str += '${bean}.${e.displayName} = $map["${displayName}"] is List ? ($map["${displayName}"] as List).map((e) => "\$e").toList() : $map["${displayName}"];';
      }else if(type?.isDartCoreInt??false){
        str += '${bean}.${e.displayName} = $map["${displayName}"]?.cast<int>();';
      }  else if(type?.element is EnumElement){
        str += '${bean}.${e.displayName} =  $map["${displayName}"] is List ? ($map["${displayName}"] as List).map((e) => ${type?.getDisplayString(withNullability: false)}.values[e is int ? e : int.parse(\'\$e\')]).toList() : $map["${displayName}"];';

      }else {
        str += '${bean}.${e.displayName} =  $map["${displayName}"] is List ? ($map["${displayName}"] as List).map((e) => ${type?.getDisplayString(withNullability: false)}.fromMap(e?.cast<String, dynamic>())).toList() : $map["${displayName}"];';
      }

    } else if(e.type.isDartCoreSet){
      InterfaceType? interfaceType;
      if(e.type is InterfaceType){
        interfaceType = e.type as InterfaceType?;
      }
      DartType? type = interfaceType?.typeArguments.firstOrNull;
      str = '// ${e} ${e.type.runtimeType} ${interfaceType?.typeArguments} ${canHaveGenerics(e.type)}$lineDelimiter';
      str += '// ${type} ${type?.isBottom} ${type?.isDartCoreObject} ${type?.isDartCoreType}$lineDelimiter';
      if(type?.isDartCoreString??false){
        str += '${bean}.${e.displayName} = $map["${displayName}"] is List ? ($map["${displayName}"] as List).map((e) => "\$e").toSet() : $map["${displayName}"];';
      }else if(type?.isDartCoreInt??false){
        str += '${bean}.${e.displayName} = $map["${displayName}"]?.cast<int>();';
      }  else if(type?.element is EnumElement){
        str += '${bean}.${e.displayName} =  $map["${displayName}"] is List ? ($map["${displayName}"] as List).map((e) => ${type?.getDisplayString(withNullability: false)}.values[e is int ? e : int.parse(\'\$e\')]).toSet() : $map["${displayName}"];';

      }else{
        str += '${bean}.${e.displayName} =  $map["${displayName}"] is List ? ($map["${displayName}"] as List).map((e) => ${type?.getDisplayString(withNullability: false)}.fromMap(e?.cast<String, dynamic>())).toSet() : $map["${displayName}"];';

      }
    } else if(e.type.isDartCoreDouble) {
      String temp = upperFirstCharacter(displayName);
      str = 'var temp$temp = $map["${displayName}"];$lineDelimiter';
      str += '${bean}.${e.displayName} = temp$temp is String ? double.tryParse(temp$temp) : temp$temp?.toDouble();';
    } else if(e.type.isDartCoreInt){
      String temp = upperFirstCharacter(displayName);
      str = 'var temp$temp = $map["${displayName}"];$lineDelimiter';
      str += '${bean}.${e.displayName} = temp$temp is String ? int.tryParse(temp$temp) : temp$temp?.toInt();';
    } else if(canHaveGenerics(e.type)){
      if(e.type.isDartCoreMap){
        InterfaceType? interfaceType;
        if(e.type is InterfaceType){
          interfaceType = e.type as InterfaceType?;
        }
        DartType? type = interfaceType?.typeArguments.firstOrNull;
        str = '// ${e} ${e.type.runtimeType} ${interfaceType?.typeArguments} ${canHaveGenerics(e.type)}$lineDelimiter';
        str += '// ${e.type.alias} ${e.type.extensionTypeErasure} ${e.type.element?.displayName} ${type} ${type?.isBottom} ${type?.isDartCoreObject} ${type?.isDartCoreType}$lineDelimiter';
        str += '${bean}.${e.displayName} =  $map["${displayName}"]?.cast<${interfaceType?.typeArguments.first}, ${interfaceType?.typeArguments.last}>();';
      }else{
        InterfaceType? interfaceType;
        if(e.type is InterfaceType){
          interfaceType = e.type as InterfaceType?;
        }
        DartType? type = interfaceType?.typeArguments.firstOrNull;
        str = '// ${e} ${e.type.runtimeType} ${interfaceType?.typeArguments} ${canHaveGenerics(e.type)}$lineDelimiter';
        str += '// ${e.type.alias} ${e.type.extensionTypeErasure} ${e.type.element?.displayName} ${type} ${type?.isBottom} ${type?.isDartCoreObject} ${type?.isDartCoreType}$lineDelimiter';
        str += '${bean}.${e.displayName} =  ${e.type.element?.displayName}.fromMap<${type?.getDisplayString(withNullability: false)}>($map["${displayName}"]);';
      }

    } else if(e.type.isDartCoreString){
      str = '${bean}.${e.displayName} = $map["${displayName}"]?.toString();';
    } else if(e.type.isDartCoreBool){
      str = '${bean}.${e.displayName} =  $map["${displayName}"];';
    } else {
      if(c.typeParameters.isNotEmpty){
        str = 'var temp${upperFirstCharacter(displayName)} = $map["${displayName}"];$lineDelimiter';
        str += '${bean}.${e.displayName} = isInt ? temp${upperFirstCharacter(displayName)} is String ? int.tryParse(temp${upperFirstCharacter(displayName)}) : temp${upperFirstCharacter(displayName)}?.toInt()'
            ' : isDouble ? temp${upperFirstCharacter(displayName)} is String ? double.tryParse(temp${upperFirstCharacter(displayName)}) : temp${upperFirstCharacter(displayName)}?.toDouble() : temp${upperFirstCharacter(displayName)};';
      }else{
        str = '// e.type.element is EnumElement ${e.type.element is EnumElement}$lineDelimiter';
        if(e.type.element is EnumElement){
          str += '${bean}.${e.displayName} =  $map["${displayName}"] == null ? null : ${e.type.getDisplayString(withNullability: false)}.values[$map["${displayName}"]];';
        }else if(e.type is DynamicType){
          str += '${bean}.${e.displayName} =  $map["${displayName}"];';
        } else{
          str += '${bean}.${e.displayName} =  ${e.type.getDisplayString(withNullability: false)}.fromMap($map["${displayName}"]?.cast<String, dynamic>());';
        }

      }

    }
    return str??"";
  }

  String handleToJson(FieldElement e, String beanName){
    String displayName = useSnakeCase ? e.displayName.snakeCase : e.displayName;
    if(e.type.isDartCoreList){
      InterfaceType? interfaceType;
      if(e.type is InterfaceType){
        interfaceType = e.type as InterfaceType?;
      }
      DartType? type = interfaceType?.typeArguments.firstOrNull;
      if(type?.isDartCoreString??false){
        return '"${displayName}":  $beanName.${e.displayName}';
      }
      if(type?.isDartCoreInt??false){
        return '"${displayName}":  $beanName.${e.displayName}';
      }
      if(type?.element is EnumElement){
        return '"${displayName}":  $beanName.${e.displayName}';
      }
      return '"$displayName":  $beanName.${e.displayName}?.map((e) => e?.${useStatic ? 'toJson' : 'toMap'}()).toList()';
    }
    if(e.type.isDartCoreSet){
      InterfaceType? interfaceType;
      if(e.type is InterfaceType){
        interfaceType = e.type as InterfaceType?;
      }
      DartType? type = interfaceType?.typeArguments.firstOrNull;
      if(type?.isDartCoreString??false){
        return '"${displayName}":  $beanName.${e.displayName}';
      }
      if(type?.isDartCoreInt??false){
        return '"${displayName}":  $beanName.${e.displayName}';
      }
      if(type?.element is EnumElement){
        return '"${displayName}":  $beanName.${e.displayName}';
      }
      return '"$displayName":  $beanName.${e.displayName}?.map((e) => e?.${useStatic ? 'toJson' : 'toMap'}()).toSet()';
    }
    return '"$displayName":  $beanName.${e.displayName}';
  }

  String handleToJsonParam(FieldElement e){
    return '${e.type} ${e.displayName}';
  }

  String lowerFirstCharacter(String s){
    return s[0].toLowerCase() + s.substring(1);
  }

  String upperFirstCharacter(String s){
    return s[0].toUpperCase() + s.substring(1);
  }

  bool canHaveGenerics(DartType type) {
    final element = type.element;
    if (element is ClassElement) {
      return element.typeParameters.isNotEmpty;
    }
    return false;
  }
}
